////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 1993-2024 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// This file is part of Octave.
//
// Octave is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Octave is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Octave; see the file COPYING.  If not, see
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <cstdio>
#include <clocale>

#include <iostream>
#include <set>
#include <string>
#include <thread>

#include "cmd-edit.h"
#include "cmd-hist.h"
#include "file-ops.h"
#include "file-stat.h"
#include "file-ops.h"
#include "fpucw-wrappers.h"
#include "lo-blas-proto.h"
#include "lo-error.h"
#include "lo-sysdep.h"
#include "oct-env.h"
#include "quit.h"
#include "str-vec.h"
#include "signal-wrappers.h"
#include "unistd-wrappers.h"

#include "builtin-defun-decls.h"
#include "defaults.h"
#include "Cell.h"
#include "defun.h"
#include "display.h"
#include "error.h"
#include "event-manager.h"
#include "graphics.h"
#include "help.h"
#include "input.h"
#include "interpreter-private.h"
#include "interpreter.h"
#include "load-path.h"
#include "load-save.h"
#include "octave.h"
#include "oct-hist.h"
#include "oct-map.h"
#include "oct-mutex.h"
#include "ovl.h"
#include "ov.h"
#include "ov-classdef.h"
#include "parse.h"
#include "pt-classdef.h"
#include "pt-eval.h"
#include "pt-jump.h"
#include "pt-stmt.h"
#include "settings.h"
#include "sighandlers.h"
#include "sysdep.h"
#include "unwind-prot.h"
#include "utils.h"
#include "variables.h"
#include "version.h"

// TRUE means the quit() call is allowed.
bool quit_allowed = true;

// TRUE means we are ready to interpret commands, but not everything
// is ready for interactive use.
bool octave_interpreter_ready = false;

// TRUE means we've processed all the init code and we are good to go.
std::atomic<bool> octave_initialized{false};

OCTAVE_BEGIN_NAMESPACE(octave)

DEFUN (__version_info__, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {retval =} __version_info__ (@var{name}, @var{version}, @var{release}, @var{date})
Undocumented internal function.
@end deftypefn */)
{
  static octave_map vinfo;

  int nargin = args.length ();

  if (nargin != 0 && nargin != 4)
    print_usage ();

  octave_value retval;

  if (nargin == 0)
    retval = vinfo;
  else if (nargin == 4)
    {
      if (vinfo.nfields () == 0)
        {
          vinfo.assign ("Name", args(0));
          vinfo.assign ("Version", args(1));
          vinfo.assign ("Release", args(2));
          vinfo.assign ("Date", args(3));
        }
      else
        {
          octave_idx_type n = vinfo.numel () + 1;

          vinfo.resize (dim_vector (n, 1));

          octave_value idx (n);

          vinfo.assign (idx, "Name", Cell (octave_value (args(0))));
          vinfo.assign (idx, "Version", Cell (octave_value (args(1))));
          vinfo.assign (idx, "Release", Cell (octave_value (args(2))));
          vinfo.assign (idx, "Date", Cell (octave_value (args(3))));
        }
    }

  return retval;
}

DEFMETHOD (quit, interp, args, ,
           doc: /* -*- texinfo -*-
@deftypefn  {} {} quit
@deftypefnx {} {} quit cancel
@deftypefnx {} {} quit force
@deftypefnx {} {} quit ("cancel")
@deftypefnx {} {} quit ("force")
@deftypefnx {} {} quit (@var{status})
@deftypefnx {} {} quit (@var{status}, "force")
@deftypefnx {} {} exit (@dots{})
Quit the current Octave session.

If the optional integer value @var{status} is supplied, pass that value to
the operating system as Octave's exit status.  The default value is zero.

When exiting, Octave will attempt to run the m-file @file{finish.m} if it
exists.  User commands to save the workspace or clean up temporary files
may be placed in that file.  Alternatively, another m-file may be scheduled
to run using @code{atexit}.  If an error occurs while executing the
@file{finish.m} file, Octave does not exit and control is returned to
the command prompt.

If the optional argument @qcode{"cancel"} is provided, Octave does not
exit and control is returned to the command prompt.  This feature allows
the @code{finish.m} file to cancel the quit process.

If the user preference to request confirmation before exiting, Octave
will display a dialog and give the user an option to cancel the exit
process.

If the optional argument @qcode{"force"} is provided, no confirmation is
requested, and the execution of the @file{finish.m} file is skipped.

Programming Note: @code{exit} is an alias for @code{quit} and can be used
interchangeably.
@seealso{atexit}
@end deftypefn */)
{
  int numel = args.length ();

  if (numel > 2)
    print_usage ();

  int exit_status = 0;

  bool force = false;
  bool cancel = false;

  if (numel == 2)
    {
      exit_status = args(0).xnint_value ("quit: STATUS must be an integer");
      std::string frc
        = args(1).xstring_value ("quit: second argument must be a string");

      if (frc == "force")
        force = true;
      else
        error (R"(quit: second argument must be string "force")");
    }
  else if (numel == 1)
    {
      if (args(0).is_string ())
        {
          const char *msg
            = R"(quit: option must be string "cancel" or "force")";

          std::string opt = args(0).xstring_value (msg);

          if (opt == "cancel")
            cancel = true;
          else if (opt == "force")
            force = true;
          else
            error ("%s", msg);
        }
      else
        exit_status = args(0).xnint_value ("quit: STATUS must be an integer");
    }

  if (cancel)
    {
      // No effect if "quit cancel" appears outside of finish script.

      if (interp.executing_finish_script ())
        interp.cancel_quit (true);

      return ovl ();
    }

  interp.quit (exit_status, force);

  return ovl ();
}

DEFALIAS (exit, quit);

DEFMETHOD (atexit, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {} atexit (@var{fcn})
@deftypefnx {} {} atexit (@var{fcn}, true)
@deftypefnx {} {} atexit (@var{fcn}, false)
@deftypefnx {} {@var{status} =} atexit (@var{fcn}, false)
Register a function to be called when Octave exits.

For example,

@example
@group
function last_words ()
  disp ("Bye bye");
endfunction
atexit ("last_words");
@end group
@end example

@noindent
will print the message @qcode{"Bye bye"} when Octave exits.

The additional argument @var{flag} will register or unregister @var{fcn}
from the list of functions to be called when Octave exits.  If @var{flag} is
true, the function is registered, and if @var{flag} is false, it is
unregistered.  For example, after registering the function @code{last_words}
above,

@example
atexit ("last_words", false);
@end example

@noindent
will remove the function from the list and Octave will not call
@code{last_words} when it exits.

The optional output @var{status} is only available when unregistering a
function.  The value is true if the unregistering was successful and false
otherwise.

Programming Note: @code{atexit} only removes the first occurrence of a function
from the list; if a function was placed in the list multiple times with
@code{atexit}, it must also be removed from the list multiple times.
@seealso{quit}
@end deftypefn */)
{
  int nargin = args.length ();

  if (nargin < 1 || nargin > 2)
    print_usage ();

  std::string arg = args(0).xstring_value ("atexit: FCN argument must be a string");

  bool add_mode = (nargin == 2)
                  ? args(1).xbool_value ("atexit: FLAG argument must be a logical value")
                  : true;

  octave_value_list retval;

  if (add_mode)
    interp.add_atexit_fcn (arg);
  else
    {
      bool found = interp.remove_atexit_fcn (arg);

      if (nargout > 0)
        retval = ovl (found);
    }

  return retval;
}

DEFMETHOD (__traditional__, interp, , ,
           doc: /* -*- texinfo -*-
@deftypefn {} {@var{tf} =} __traditional__ ()
Return true if Octave was invoked with the @w{@env{--traditional}}@ option.
@end deftypefn */)
{
  return ovl (interp.traditional ());
}

temporary_file_list::~temporary_file_list ()
{
  cleanup ();
}

void
temporary_file_list::insert (const std::string& file)
{
  m_files.insert (file);
}

void
temporary_file_list::cleanup ()
{
  while (! m_files.empty ())
    {
      auto it = m_files.begin ();

      octave_unlink_wrapper (it->c_str ());

      m_files.erase (it);
    }
}

// The time we last time we changed directories.
sys::time Vlast_chdir_time = 0.0;

static void
initialize_version_info ()
{
  octave_value_list args (4);

  args(0) = "GNU Octave";
  args(1) = OCTAVE_VERSION;
  args(2) = config::release ();
  args(3) = OCTAVE_RELEASE_DATE;

  F__version_info__ (args);
}

static void
xerbla_abort ()
{
  error ("Fortran procedure terminated by call to XERBLA");
}

static void
initialize_xerbla_error_handler ()
{
  // The idea here is to force xerbla to be referenced so that we will
  // link to our own version instead of the one provided by the BLAS
  // library.  But numeric_limits<double>::NaN () should never be -1, so
  // we should never actually call xerbla.  FIXME (again!): If this
  // becomes a constant expression the test might be optimized away and
  // then the reference to the function might also disappear.

  if (numeric_limits<double>::NaN () == -1)
    F77_FUNC (xerbla, XERBLA) ("octave", 13 F77_CHAR_ARG_LEN (6));

  typedef void (*xerbla_handler_ptr) ();

  typedef void (*octave_set_xerbla_handler_ptr) (xerbla_handler_ptr);

  dynamic_library libs ("");

  if (libs)
    {
      octave_set_xerbla_handler_ptr octave_set_xerbla_handler
        = reinterpret_cast<octave_set_xerbla_handler_ptr>
          (libs.search ("octave_set_xerbla_handler"));

      if (octave_set_xerbla_handler)
        octave_set_xerbla_handler (xerbla_abort);
    }
}

OCTAVE_NORETURN static void
lo_error_handler (const char *fmt, ...)
{
  va_list args;
  va_start (args, fmt);
  verror_with_cfn (fmt, args);
  va_end (args);

  throw execution_exception ();
}

OCTAVE_NORETURN static void
lo_error_with_id_handler (const char *id, const char *fmt, ...)
{
  va_list args;
  va_start (args, fmt);
  verror_with_id_cfn (id, fmt, args);
  va_end (args);

  throw execution_exception ();
}

static void
initialize_error_handlers ()
{
  set_liboctave_error_handler (lo_error_handler);
  set_liboctave_error_with_id_handler (lo_error_with_id_handler);
  set_liboctave_warning_handler (warning);
  set_liboctave_warning_with_id_handler (warning_with_id);
}

// Create an interpreter object and perform initialization up to the
// point of setting reading command history and setting the load
// path.

interpreter::interpreter (application *app_context)
  : m_app_context (app_context),
    m_tmp_files (),
    m_atexit_fcns (),
    m_display_info (),
    m_environment (),
    m_settings (),
    m_error_system (*this),
    m_evaluator (*this),
    m_help_system (*this),
    m_input_system (*this),
    m_output_system (*this),
    m_history_system (*this),
    m_dynamic_loader (*this),
    m_load_path (*this),
    m_load_save_system (*this),
    m_type_info (),
    m_symbol_table (*this),
    m_stream_list (*this),
    m_child_list (),
    m_url_handle_manager (),
    m_cdef_manager (*this),
    m_gtk_manager (*this),
    m_event_manager (*this),
    m_gh_manager (nullptr),
    m_interactive (false),
    m_read_site_files (true),
    m_read_init_files (m_app_context != nullptr),
    m_verbose (false),
    m_traditional (false),
    m_inhibit_startup_message (false),
    m_load_path_initialized (false),
    m_history_initialized (false),
    m_interrupt_all_in_process_group (true),
    m_cancel_quit (false),
    m_executing_finish_script (false),
    m_executing_atexit (false),
    m_initialized (false)
{
  // FIXME: When thread_local storage is used by default, this message
  // should change to say something like
  //
  //   only one Octave interpreter may be active in any given thread

  if (s_instance)
    throw std::runtime_error
    ("only one Octave interpreter may be active");

  s_instance = this;

#if defined (OCTAVE_HAVE_WINDOWS_UTF8_LOCALE)
  // Force a UTF-8 locale on Windows if possible
  std::setlocale (LC_ALL, ".UTF8");
#else
  std::setlocale (LC_ALL, "");
#endif
  // Matlab uses "C" locale for LC_NUMERIC class regardless of local setting
  std::setlocale (LC_NUMERIC, "C");
  std::setlocale (LC_TIME, "C");
  sys::env::putenv ("LC_NUMERIC", "C");
  sys::env::putenv ("LC_TIME", "C");

  // Initialize the default floating point unit control state.
  octave_set_default_fpucw ();

  thread::init ();

  octave_ieee_init ();

  initialize_xerbla_error_handler ();

  initialize_error_handlers ();

  if (m_app_context)
    {
      install_signal_handlers ();
      octave_unblock_signal_by_name ("SIGTSTP");
    }
  else
    quit_allowed = false;

  if (! m_app_context)
    m_display_info.initialize ();

  bool line_editing = false;

  if (m_app_context)
    {
      // Embedded interpreters don't execute command line options.
      const cmdline_options& options = m_app_context->options ();

      // Make all command-line arguments available to startup files,
      // including PKG_ADD files.

      string_vector args = options.all_args ();

      m_app_context->intern_argv (args);
      intern_nargin (args.numel () - 1);

      bool is_octave_program = m_app_context->is_octave_program ();

      std::list<std::string> command_line_path = options.command_line_path ();

      for (const auto& pth : command_line_path)
        m_load_path.set_command_line_path (pth);

      std::string exec_path = options.exec_path ();
      if (! exec_path.empty ())
        m_environment.exec_path (exec_path);

      std::string image_path = options.image_path ();
      if (! image_path.empty ())
        m_environment.image_path (image_path);

      if (! options.no_window_system ())
        m_display_info.initialize ();

      // Is input coming from a terminal?  If so, we are probably
      // interactive.

      // If stdin is not a tty, then we are reading commands from a
      // pipe or a redirected file.
      bool stdin_is_tty = octave_isatty_wrapper (fileno (stdin));

      m_interactive = (! is_octave_program && stdin_is_tty
                       && octave_isatty_wrapper (fileno (stdout)));

      // Don't force interactive if we're already interactive (bug #60696).
      bool forced_interactive = options.forced_interactive ();
      if (m_interactive)
        {
          m_app_context->forced_interactive (false);
          forced_interactive = false;
        }

      // Check if the user forced an interactive session.
      if (forced_interactive)
        m_interactive = true;

      line_editing = options.line_editing ();
      if ((! m_interactive || forced_interactive)
          && ! options.forced_line_editing ())
        line_editing = false;

      m_traditional = options.traditional ();

      // FIXME: if possible, perform the following actions directly
      // instead of using the interpreter-level functions.

      if (options.echo_commands ())
        m_evaluator.echo
        (tree_evaluator::ECHO_SCRIPTS | tree_evaluator::ECHO_FUNCTIONS
         | tree_evaluator::ECHO_ALL);

      std::string docstrings_file = options.docstrings_file ();
      if (! docstrings_file.empty ())
        Fbuilt_in_docstrings_file (*this, ovl (docstrings_file));

      std::string doc_cache_file = options.doc_cache_file ();
      if (! doc_cache_file.empty ())
        Fdoc_cache_file (*this, ovl (doc_cache_file));

      std::string info_file = options.info_file ();
      if (! info_file.empty ())
        Finfo_file (*this, ovl (info_file));

      std::string info_program = options.info_program ();
      if (! info_program.empty ())
        Finfo_program (*this, ovl (info_program));

      std::string texi_macros_file = options.texi_macros_file ();
      if (! texi_macros_file.empty ())
        Ftexi_macros_file (*this, ovl (texi_macros_file));
    }

  // FIXME: we defer creation of the gh_manager object because it
  // creates a root_figure object that requires the display_info
  // object, but that is currently only accessible through the global
  // interpreter object and that is not available until after the
  // interpreter::instance pointer is set (above).  It would be better
  // if m_gh_manager could be an object value instead of a pointer and
  // created as part of the interpreter initialization.  To do that,
  // we should either make the display_info object independent of the
  // interpreter object (does it really need to cache any
  // information?) or defer creation of the root_figure object until
  // it is actually needed.
  m_gh_manager = new gh_manager (*this);

  m_input_system.initialize (line_editing);

  // These can come after command line args since none of them set any
  // defaults that might be changed by command line options.

  initialize_version_info ();

  // This should be done before initializing the load path because
  // some PKG_ADD files might need --traditional behavior.

  if (m_traditional)
    maximum_braindamage ();

  octave_interpreter_ready = true;
}

OCTAVE_THREAD_LOCAL interpreter *interpreter::s_instance = nullptr;

interpreter::~interpreter ()
{
  if (! m_app_context)
    shutdown ();

  delete m_gh_manager;
}

void
interpreter::intern_nargin (octave_idx_type nargs)
{
  m_evaluator.set_auto_fcn_var (stack_frame::NARGIN, nargs);
}

// Read the history file unless a command-line option inhibits that.

void
interpreter::initialize_history (bool read_history_file)
{
  if (! m_history_initialized)
    {
      // Allow command-line option to override.

      if (m_app_context)
        {
          const cmdline_options& options = m_app_context->options ();

          read_history_file = options.read_history_file ();

          if (! read_history_file)
            command_history::ignore_entries ();
        }

      m_history_system.initialize (read_history_file);

      if (! m_app_context)
        command_history::ignore_entries ();

      m_history_initialized = true;
    }
}

// Set the initial path to the system default unless command-line
// option says to leave it empty.

void
interpreter::initialize_load_path (bool set_initial_path)
{
  if (! m_load_path_initialized)
    {
      // Allow command-line option to override.

      if (m_app_context)
        {
          const cmdline_options& options = m_app_context->options ();

          set_initial_path = options.set_initial_path ();
        }

      // Temporarily set the execute_pkg_add function to one that
      // catches exceptions.  This is better than wrapping
      // load_path::initialize in a try-catch block because it will
      // not stop executing PKG_ADD files at the first exception.
      // It's also better than changing the default execute_pkg_add
      // function to use safe_source file because that will normally
      // be evaluated from the normal interpreter loop where exceptions
      // are already handled.

      unwind_action restore_add_hook (&load_path::set_add_hook, &m_load_path,
                                      m_load_path.get_add_hook ());

      m_load_path.set_add_hook ([=] (const std::string& dir)
      { this->execute_pkg_add (dir); });

      m_load_path.initialize (set_initial_path);

      m_load_path_initialized = true;
    }
}

// This may be called separately from execute

void
interpreter::initialize ()
{
  if (m_initialized)
    return;

  if (m_app_context)
    {
      const cmdline_options& options = m_app_context->options ();

      if (options.experimental_terminal_widget ())
        {
          if (! options.gui ())
            display_startup_message ();
        }
      else
        display_startup_message ();
    }
  else
    display_startup_message ();

  // Wait to read the history file until the interpreter reads input
  // files and begins evaluating commands.

  initialize_history ();

  // Initializing the load path may execute PKG_ADD files, so can't be
  // done until the interpreter is ready to execute commands.

  // Deferring it to the execute step also allows the path to be
  // initialized between creating and execute the interpreter, for
  // example, to set a custom path for an embedded interpreter.

  initialize_load_path ();

  octave_save_signal_mask ();

  can_interrupt = true;

  octave_signal_hook = respond_to_pending_signals;
  octave_interrupt_hook = nullptr;

  catch_interrupts ();

  // FIXME: could we eliminate this variable or make it not be global?
  // Global used to communicate with signal handler.
  octave_initialized = true;

  m_initialized = true;
}

// Note: this function is currently only used with the new
// experimental terminal widget.

void
interpreter::get_line_and_eval ()
{
  m_evaluator.get_line_and_eval ();
}

// Note: the following class is currently only used with the new
// experimental terminal widget.

class cli_input_reader
{
public:

  cli_input_reader (interpreter& interp)
    : m_interpreter (interp), m_thread () { }

  OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (cli_input_reader)

  ~cli_input_reader ()
  {
    // FIXME: Would it be better to ensure that
    // interpreter::get_line_and_eval exits and then call
    // m_thread.join () here?

    m_thread.detach ();
  }

  void start ()
  {
    m_thread = std::thread (&interpreter::get_line_and_eval, &m_interpreter);
  }

private:

  interpreter& m_interpreter;

  std::thread m_thread;
};

void
interpreter::parse_and_execute (const std::string& input,
                                bool& incomplete_parse)
{
  m_evaluator.parse_and_execute (input, incomplete_parse);
}

// FIXME: this function is intended to be executed only once.  Should
// we enforce that restriction?

int
interpreter::execute ()
{
  int exit_status = 0;

  try
    {
      initialize ();

      execute_startup_files ();

      if (m_app_context)
        {
          const cmdline_options& options = m_app_context->options ();

          if (m_app_context->have_eval_option_code ())
            {
              int status = execute_eval_option_code ();

              if (status )
                exit_status = status;

              if (! options.persist ())
                {
                  shutdown ();

                  return exit_status;
                }
            }

          // If there is an extra argument, see if it names a file to
          // read.  Additional arguments are taken as command line options
          // for the script.

          if (m_app_context->have_script_file ())
            {
              int status = execute_command_line_file ();

              if (status)
                exit_status = status;

              if (! options.persist ())
                {
                  shutdown ();

                  return exit_status;
                }
            }

          if (options.forced_interactive ())
            command_editor::blink_matching_paren (false);

          if (options.server ())
            exit_status = server_loop ();
          else if (options.experimental_terminal_widget ())
            {
              if (options.gui ())
                {
                  m_event_manager.start_gui (true);

                  exit_status = server_loop ();
                }
              else
                {
                  // Use an object so that the thread started for the
                  // reader will be cleaned up no matter how we exit
                  // this function.

                  cli_input_reader reader (*this);

                  reader.start ();

                  exit_status = server_loop ();
                }
            }
          else
            exit_status = main_loop ();

          shutdown ();
        }
    }
  catch (const exit_exception& xe)
    {
      exit_status = xe.exit_status ();

      shutdown ();
    }

  return exit_status;
}

// Call a function with exceptions handled to avoid problems with
// errors while shutting down.

#define OCTAVE_IGNORE_EXCEPTION(E)                                      \
  catch (E)                                                             \
    {                                                                   \
      recover_from_exception ();                                        \
                                                                        \
      std::cerr << "error: ignoring " #E " while preparing to exit"     \
                << std::endl;                                           \
    }

#define OCTAVE_SAFE_CALL(F, ARGS)                                       \
  do                                                                    \
    {                                                                   \
      try                                                               \
        {                                                               \
          unwind_action restore_debug_on_error                          \
            (&error_system::set_debug_on_error, &m_error_system,        \
             m_error_system.debug_on_error ());                        \
                                                                        \
          unwind_action restore_debug_on_warning                        \
            (&error_system::set_debug_on_warning, &m_error_system,      \
             m_error_system.debug_on_warning ());                       \
                                                                        \
          m_error_system.debug_on_error (false);                        \
          m_error_system.debug_on_warning (false);                      \
                                                                        \
          F ARGS;                                                       \
        }                                                               \
      OCTAVE_IGNORE_EXCEPTION (const exit_exception&)                   \
      OCTAVE_IGNORE_EXCEPTION (const interrupt_exception&)              \
      OCTAVE_IGNORE_EXCEPTION (const execution_exception&)              \
      OCTAVE_IGNORE_EXCEPTION (const std::bad_alloc&)                   \
    }                                                                   \
  while (0)

void
interpreter::shutdown ()
{
  // Attempt to prevent more than one call to shutdown.

  if (! m_initialized)
    return;

  m_initialized = false;

  OCTAVE_SAFE_CALL (feval, ("close", ovl ("all"), 0));

  // Any atexit functions added after this function call won't be
  // executed.  Each atexit function is executed with
  // OCTAVE_SAFE_CALL, so we don't need that here.

  execute_atexit_fcns ();

  // Clear all functions and variables while the event manager is
  // still processing events and notify the event manager.  This way,
  // the workspace model will be cleared before the GUI exits.

  // FIXME: This approach seems a bit fragile since there could be
  // other places in the GUI that have references to interpreter
  // objects.  How can we reliably ensure that they are all removed
  // before the interpreter exits?  Maybe the best solution is to
  // always start the GUI from the interpreter and close it when the
  // interpreter exits?  However, the workspace model is owned by the
  // base_qobject object not the workspace viewer or the main window,
  // so simply closing the GUI window(s) is not sufficient.  See also
  // bug #61994.

  // Note that we don't force symbols to be cleared, so we will
  // respect mlock at this point.  Later, we'll force all variables
  // and functions to be cleared.

  OCTAVE_SAFE_CALL (clear_all, ());
  OCTAVE_SAFE_CALL (m_event_manager.clear_workspace, ());

  // If we are attached to a GUI, queue and event to close it (only
  // works with the new terminal widget), process pending events and
  // disable the link.

  OCTAVE_SAFE_CALL (m_event_manager.close_gui, ());
  OCTAVE_SAFE_CALL (m_event_manager.process_events, (true));
  OCTAVE_SAFE_CALL (m_event_manager.disable, ());

  OCTAVE_SAFE_CALL (m_input_system.clear_input_event_hooks, ());

  // We may still have some figures.  Close them.

  OCTAVE_SAFE_CALL (feval, ("close", ovl ("all"), 0));

  // What is supposed to happen if a figure has a closerequestfcn or
  // deletefcn callback registered that creates other figures or
  // variables?  What if those variables are classdef objects with
  // destructors that can create figures?  The possibilities are
  // endless.  At some point, we have to give up and force execution
  // to end.

  // Note that we again don't force symbols to be cleared, so we
  // continue to respect mlock here.  Later, we'll force all variables
  // and functions to be cleared.

  OCTAVE_SAFE_CALL (clear_all, ());

  // Do this explicitly so that destructors for mex file objects
  // are called, so that functions registered with mexAtExit are
  // called.

  OCTAVE_SAFE_CALL (m_symbol_table.clear_mex_functions, ());

  OCTAVE_SAFE_CALL (command_editor::restore_terminal_state, ());

  OCTAVE_SAFE_CALL (m_history_system.write_timestamp, ());

  if (! command_history::ignoring_entries ())
    OCTAVE_SAFE_CALL (command_history::clean_up_and_save, ());

  OCTAVE_SAFE_CALL (m_gtk_manager.unload_all_toolkits, ());

  // Now that the graphics toolkits have been unloaded, force all
  // symbols to be cleared.

  OCTAVE_SAFE_CALL (clear_all, (true));

  // FIXME:  May still need something like this to ensure that
  // destructors for class objects will run properly.  Should that be
  // done earlier?  Before or after atexit functions are executed?
  // What will happen if the destructor for an obect attempts to
  // display a figure?

  OCTAVE_SAFE_CALL (m_symbol_table.cleanup, ());

  OCTAVE_SAFE_CALL (sysdep_cleanup, ());

  OCTAVE_SAFE_CALL (flush_stdout, ());

  // Don't call singleton_cleanup_list::cleanup until we have the
  // problems with registering/unregistering types worked out.  For
  // example, uncomment the following line, then use the make_int
  // function from the examples directory to create an integer
  // object and then exit Octave.  Octave should crash with a
  // segfault when cleaning up the typinfo singleton.  We need some
  // way to force new octave_value_X types that are created in
  // .oct files to be unregistered when the .oct file shared library
  // is unloaded.
  //
  // OCTAVE_SAFE_CALL (singleton_cleanup_list::cleanup, ());
}

void
interpreter::execute_atexit_fcns ()
{
  // Prevent atexit functions from adding new functions to the list.
  m_executing_atexit = true;

  while (! m_atexit_fcns.empty ())
    {
      std::string fcn = m_atexit_fcns.front ();

      m_atexit_fcns.pop_front ();

      OCTAVE_SAFE_CALL (feval, (fcn, octave_value_list (), 0));

      OCTAVE_SAFE_CALL (flush_stdout, ());
    }
}

void
interpreter::display_startup_message () const
{
  bool inhibit_startup_message = false;

  if (m_app_context)
    {
      const cmdline_options& options = m_app_context->options ();

      inhibit_startup_message = options.inhibit_startup_message ();
    }

  if (m_interactive && ! inhibit_startup_message)
    std::cout << octave_startup_message () << "\n" << std::endl;
}

// Initialize by reading startup files.  Return nonzero if an exception
// occurs when reading any of them, but don't exit early because of an
// exception.

int
interpreter::execute_startup_files ()
{
  bool read_site_files = m_read_site_files;
  bool read_init_files = m_read_init_files;
  bool verbose = m_verbose;
  bool inhibit_startup_message = m_inhibit_startup_message;

  if (m_app_context)
    {
      const cmdline_options& options = m_app_context->options ();

      read_site_files = options.read_site_files ();
      read_init_files = options.read_init_files ();
      verbose = options.verbose_flag ();
      inhibit_startup_message = options.inhibit_startup_message ();
    }

  verbose = (verbose && ! inhibit_startup_message);

  bool require_file = false;

  std::string context;

  int exit_status = 0;

  if (read_site_files)
    {
      // Execute commands from the site-wide configuration file.
      // First from the file $(prefix)/lib/octave/site/m/octaverc
      // (if it exists), then from the file
      // $(prefix)/share/octave/$(version)/m/octaverc (if it exists).

      int status = safe_source_file (config::local_site_defaults_file (),
                                     context, verbose, require_file);

      if (status)
        exit_status = status;

      status = safe_source_file (config::site_defaults_file (),
                                 context, verbose, require_file);

      if (status)
        exit_status = status;
    }

  if (read_init_files)
    {
      // Try to execute commands from the Matlab compatible startup.m file
      // if it exists anywhere in the load path when starting Octave.
      std::string ff_startup_m = file_in_path ("startup.m", "");

      if (! ff_startup_m.empty ())
        {
          int parse_status = 0;

          try
            {
              eval_string (std::string ("startup"), false, parse_status, 0);
            }
          catch (const interrupt_exception&)
            {
              recover_from_exception ();
            }
          catch (const execution_exception& ee)
            {
              handle_exception (ee);
            }
        }

      // Try to execute commands from $CONFIG/octave/octaverc, where
      // $CONFIG is the platform-dependent location for user local
      // configuration files.

      std::string user_config_dir = sys::env::get_user_config_directory ();

      std::string cfg_dir = user_config_dir + sys::file_ops::dir_sep_str ()
                            + "octave";

      std::string cfg_rc = sys::env::make_absolute ("octaverc", cfg_dir);

      if (! cfg_rc.empty ())
        {
          int status = safe_source_file (cfg_rc, context, verbose,
                                         require_file);

          if (status)
            exit_status = status;
        }

      // Try to execute commands from $HOME/$OCTAVE_INITFILE and
      // $OCTAVE_INITFILE.  If $OCTAVE_INITFILE is not set,
      // .octaverc is assumed.

      bool home_rc_already_executed = false;

      std::string initfile = sys::env::getenv ("OCTAVE_INITFILE");

      if (initfile.empty ())
        initfile = ".octaverc";

      std::string home_dir = sys::env::get_home_directory ();

      std::string home_rc = sys::env::make_absolute (initfile, home_dir);

      std::string local_rc;

      if (! home_rc.empty ())
        {
          int status = safe_source_file (home_rc, context, verbose,
                                         require_file);

          if (status)
            exit_status = status;

          // Names alone are not enough.

          if (sys::file_exists (home_rc))
            {
              // We want to check for curr_dir after executing home_rc
              // because doing that may change the working directory.

              local_rc = sys::env::make_absolute (initfile);

              home_rc_already_executed = sys::same_file (home_rc, local_rc);
            }
        }

      if (! home_rc_already_executed)
        {
          if (local_rc.empty ())
            local_rc = sys::env::make_absolute (initfile);

          int status = safe_source_file (local_rc, context, verbose,
                                         require_file);

          if (status)
            exit_status = status;
        }
    }

  if (m_interactive && verbose)
    std::cout << std::endl;

  return exit_status;
}

// Execute any code specified with --eval 'CODE'

int
interpreter::execute_eval_option_code ()
{
  if (! m_app_context)
    return 0;

  const cmdline_options& options = m_app_context->options ();

  std::string code_to_eval = options.code_to_eval ();

  unwind_protect_var<bool> upv (m_interactive, false);

  int parse_status = 0;

  try
    {
      eval_string (code_to_eval, false, parse_status, 0);
    }
  catch (const interrupt_exception&)
    {
      recover_from_exception ();

      return 1;
    }
  catch (const execution_exception& ee)
    {
      handle_exception (ee);

      return 1;
    }

  return parse_status;
}

int
interpreter::execute_command_line_file ()
{
  if (! m_app_context)
    return 0;

  const cmdline_options& options = m_app_context->options ();

  string_vector args = options.all_args ();

  void (interpreter::*interactive_fptr) (bool) = &interpreter::interactive;
  unwind_action restore_interactive (interactive_fptr, this, m_interactive);

  unwind_action restore_argv (&application::intern_argv, m_app_context, args);

  unwind_action restore_nargin (&interpreter::intern_nargin, this,
                                args.numel () - 1);

  void (application::*program_invocation_name_fptr) (const std::string&)
    = &application::program_invocation_name;
  unwind_action restore_program_invocation_name
  (program_invocation_name_fptr, m_app_context,
   application::program_invocation_name ());

  void (application::*program_name_fptr) (const std::string&)
    = &application::program_name;
  unwind_action restore_program_name
  (program_name_fptr, m_app_context, application::program_name ());

  m_interactive = false;

  // If we are running an executable script (#! /bin/octave) then
  // we should only see the args passed to the script.

  string_vector script_args = options.remaining_args ();

  m_app_context->intern_argv (script_args);
  intern_nargin (script_args.numel () - 1);

  std::string fname = script_args[0];

  m_app_context->set_program_names (fname);

  std::string context;
  bool verbose = false;
  bool require_file = true;

  return safe_source_file (fname, context, verbose, require_file);
}

int
interpreter::main_loop ()
{
  command_editor::add_event_hook (release_unreferenced_dynamic_libraries);

  return m_evaluator.repl ();
}

int
interpreter::server_loop ()
{
  return m_evaluator.server_loop ();
}

tree_evaluator&
interpreter::get_evaluator ()
{
  return m_evaluator;
}

stream_list&
interpreter::get_stream_list ()
{
  return m_stream_list;
}

url_handle_manager&
interpreter::get_url_handle_manager ()
{
  return m_url_handle_manager;
}

symbol_scope
interpreter::get_top_scope () const
{
  return m_evaluator.get_top_scope ();
}

symbol_scope
interpreter::get_current_scope () const
{
  return m_evaluator.get_current_scope ();
}

symbol_scope
interpreter::require_current_scope (const std::string& who) const
{
  symbol_scope scope = get_current_scope ();

  if (! scope)
    error ("%s: symbol table scope missing", who.c_str ());

  return scope;
}

profiler&
interpreter::get_profiler ()
{
  return m_evaluator.get_profiler ();
}

int
interpreter::chdir (const std::string& dir)
{
  std::string xdir = sys::file_ops::tilde_expand (dir);

  int cd_ok = sys::env::chdir (xdir);

  if (! cd_ok)
    error ("%s: %s", dir.c_str (), std::strerror (errno));

  Vlast_chdir_time.stamp ();

  // FIXME: should these actions be handled as a list of functions
  // to call so users can add their own chdir handlers?

  m_load_path.read_dir_config (".");
  m_load_path.update ();
  // Updating the last prompt time stamp avoids skipping a fcn-info refresh
  // so that functions in the new current directory can shadow functions
  // further back in the load path order.
  Vlast_prompt_time.stamp ();

  m_event_manager.directory_changed (sys::env::get_current_directory ());

  return cd_ok;
}

void
interpreter::mlock (bool skip_first) const
{
  m_evaluator.mlock (skip_first);
}

void
interpreter::munlock (bool skip_first) const
{
  m_evaluator.munlock (skip_first);
}

bool
interpreter::mislocked (bool skip_first) const
{
  return m_evaluator.mislocked (skip_first);
}

void
interpreter::munlock (const char *nm)
{
  if (! nm)
    error ("munlock: invalid value for NAME");

  munlock (std::string (nm));
}

void
interpreter::munlock (const std::string& nm)
{
  octave_value val = m_symbol_table.find_function (nm);

  if (val.is_defined ())
    {
      octave_function *fcn = val.function_value ();

      if (fcn)
        fcn->unlock ();
    }
}

bool
interpreter::mislocked (const char *nm)
{
  if (! nm)
    error ("mislocked: invalid value for NAME");

  return mislocked (std::string (nm));
}

bool
interpreter::mislocked (const std::string& nm)
{
  bool retval = false;

  octave_value val = m_symbol_table.find_function (nm);

  if (val.is_defined ())
    {
      octave_function *fcn = val.function_value ();

      if (fcn)
        retval = fcn->islocked ();
    }

  return retval;
}

std::string
interpreter::mfilename (const std::string& opt) const
{
  return m_evaluator.mfilename (opt);
}

octave_value_list
interpreter::eval_string (const std::string& eval_str,
                          bool silent, int& parse_status,
                          int nargout)
{
  return m_evaluator.eval_string (eval_str, silent, parse_status, nargout);
}

octave_value
interpreter::eval_string (const std::string& eval_str,
                          bool silent, int& parse_status)
{
  return m_evaluator.eval_string (eval_str, silent, parse_status);
}

octave_value_list
interpreter::eval_string (const octave_value& arg,
                          bool silent, int& parse_status,
                          int nargout)
{
  return m_evaluator.eval_string (arg, silent, parse_status, nargout);
}

octave_value_list
interpreter::eval (const std::string& try_code,
                   int nargout)
{
  return m_evaluator.eval (try_code, nargout);
}

octave_value_list
interpreter::eval (const std::string& try_code,
                   const std::string& catch_code,
                   int nargout)
{
  return m_evaluator.eval (try_code, catch_code, nargout);
}

octave_value_list
interpreter::evalin (const std::string& context,
                     const std::string& try_code,
                     int nargout)
{
  return m_evaluator.evalin (context, try_code, nargout);
}

octave_value_list
interpreter::evalin (const std::string& context,
                     const std::string& try_code,
                     const std::string& catch_code,
                     int nargout)
{
  return m_evaluator.evalin (context, try_code, catch_code, nargout);
}

//! Evaluate an Octave function (built-in or interpreted) and return
//! the list of result values.
//!
//! @param name The name of the function to call.
//! @param args The arguments to the function.
//! @param nargout The number of output arguments expected.
//! @return A list of output values.  The length of the list is not
//!         necessarily the same as @c nargout.

octave_value_list
interpreter::feval (const char *name,
                    const octave_value_list& args,
                    int nargout)
{
  return feval (std::string (name), args, nargout);
}

octave_value_list
interpreter::feval (const std::string& name,
                    const octave_value_list& args,
                    int nargout)
{
  octave_value fcn = m_symbol_table.find_function (name, args);

  if (fcn.is_undefined ())
    error ("feval: function '%s' not found", name.c_str ());

  octave_function *of = fcn.function_value ();

  return of->call (m_evaluator, nargout, args);
}

octave_value_list
interpreter::feval (octave_function *fcn,
                    const octave_value_list& args,
                    int nargout)
{
  if (fcn)
    return fcn->call (m_evaluator, nargout, args);

  return octave_value_list ();
}

octave_value_list
interpreter::feval (const octave_value& val,
                    const octave_value_list& args,
                    int nargout)
{
  // FIXME: do we really want to silently return an empty ovl if
  // the function object is undefined?  It's essentially what the
  // version above that accepts a pointer to an octave_function
  // object does and some code was apparently written to rely on it
  // (for example, __ode15__).

  if (val.is_undefined ())
    return ovl ();

  if (val.is_function ())
    {
      return feval (val.function_value (), args, nargout);
    }
  else if (val.is_function_handle () || val.is_inline_function ())
    {
      // This covers function handles, inline functions, and anonymous
      //  functions.

      std::list<octave_value_list> arg_list;
      arg_list.push_back (args);

      // FIXME: could we make octave_value::subsref a const method?
      // It would be difficult because there are instances of
      // incrementing the reference count inside subsref methods,
      // which means they can't be const with the current way of
      // handling reference counting.

      octave_value xval = val;
      return xval.subsref ("(", arg_list, nargout);
    }
  else if (val.is_string ())
    {
      return feval (val.string_value (), args, nargout);
    }
  else
    error ("feval: first argument must be a string, inline function, or a function handle");

  return ovl ();
}

//! Evaluate an Octave function (built-in or interpreted) and return
//! the list of result values.
//!
//! @param args The first element of @c args is the function to call.
//!             It may be the name of the function as a string, a function
//!             handle, or an inline function.  The remaining arguments are
//!             passed to the function.
//! @param nargout The number of output arguments expected.
//! @return A list of output values.  The length of the list is not
//!         necessarily the same as @c nargout.

octave_value_list
interpreter::feval (const octave_value_list& args,
                    int nargout)
{
  if (args.length () == 0)
    error ("feval: first argument must be a string, inline function, or a function handle");

  octave_value f_arg = args(0);

  octave_value_list tmp_args = args.slice (1, args.length () - 1, true);

  return feval (f_arg, tmp_args, nargout);
}

octave_value
interpreter::make_function_handle (const std::string& name)
{
  return m_evaluator.make_fcn_handle (name);
}

void
interpreter::install_variable (const std::string& name,
                               const octave_value& value, bool global)
{
  m_evaluator.install_variable (name, value, global);
}

octave_value
interpreter::global_varval (const std::string& name) const
{
  return m_evaluator.global_varval (name);
}

void
interpreter::global_assign (const std::string& name,
                            const octave_value& val)
{
  m_evaluator.global_assign (name, val);
}

octave_value
interpreter::top_level_varval (const std::string& name) const
{
  return m_evaluator.top_level_varval (name);
}

void
interpreter::top_level_assign (const std::string& name,
                               const octave_value& val)
{
  m_evaluator.top_level_assign (name, val);
}

bool
interpreter::is_variable (const std::string& name) const
{
  return m_evaluator.is_variable (name);
}

bool
interpreter::is_local_variable (const std::string& name) const
{
  return m_evaluator.is_local_variable (name);
}

octave_value
interpreter::varval (const std::string& name) const
{
  return m_evaluator.varval (name);
}

void
interpreter::assign (const std::string& name,
                     const octave_value& val)
{
  m_evaluator.assign (name, val);
}

void
interpreter::assignin (const std::string& context,
                       const std::string& name,
                       const octave_value& val)
{
  m_evaluator.assignin (context, name, val);
}

void
interpreter::source_file (const std::string& file_name,
                          const std::string& context, bool verbose,
                          bool require_file)
{
  m_evaluator.source_file (file_name, context, verbose, require_file);
}

bool
interpreter::at_top_level () const
{
  return m_evaluator.at_top_level ();
}

bool
interpreter::isglobal (const std::string& name) const
{
  return m_evaluator.is_global (name);
}

octave_value
interpreter::find (const std::string& name)
{
  return m_evaluator.find (name);
}

void
interpreter::clear_all (bool force)
{
  m_evaluator.clear_all (force);
}

void
interpreter::clear_objects ()
{
  m_evaluator.clear_objects ();
}

void
interpreter::clear_variable (const std::string& name)
{
  m_evaluator.clear_variable (name);
}

void
interpreter::clear_variable_pattern (const std::string& pattern)
{
  m_evaluator.clear_variable_pattern (pattern);
}

void
interpreter::clear_variable_regexp (const std::string& pattern)
{
  m_evaluator.clear_variable_regexp (pattern);
}

void
interpreter::clear_variables ()
{
  m_evaluator.clear_variables ();
}

void
interpreter::clear_global_variable (const std::string& name)
{
  m_evaluator.clear_global_variable (name);
}

void
interpreter::clear_global_variable_pattern (const std::string& pattern)
{
  m_evaluator.clear_global_variable_pattern (pattern);
}

void
interpreter::clear_global_variable_regexp (const std::string& pattern)
{
  m_evaluator.clear_global_variable_regexp (pattern);
}

void
interpreter::clear_global_variables ()
{
  m_evaluator.clear_global_variables ();
}

void
interpreter::clear_functions (bool force)
{
  m_symbol_table.clear_functions (force);
}

void
interpreter::clear_function (const std::string& name)
{
  m_symbol_table.clear_function (name);
}

void
interpreter::clear_symbol (const std::string& name)
{
  m_evaluator.clear_symbol (name);
}

void
interpreter::clear_function_pattern (const std::string& pat)
{
  m_symbol_table.clear_function_pattern (pat);
}

void
interpreter::clear_function_regexp (const std::string& pat)
{
  m_symbol_table.clear_function_regexp (pat);
}

void
interpreter::clear_symbol_pattern (const std::string& pat)
{
  return m_evaluator.clear_symbol_pattern (pat);
}

void
interpreter::clear_symbol_regexp (const std::string& pat)
{
  return m_evaluator.clear_symbol_regexp (pat);
}

std::list<std::string>
interpreter::global_variable_names ()
{
  return m_evaluator.global_variable_names ();
}

std::list<std::string>
interpreter::top_level_variable_names ()
{
  return m_evaluator.top_level_variable_names ();
}

std::list<std::string>
interpreter::variable_names ()
{
  return m_evaluator.variable_names ();
}

std::list<std::string>
interpreter::user_function_names ()
{
  return m_symbol_table.user_function_names ();
}

std::list<std::string>
interpreter::autoloaded_functions () const
{
  return m_evaluator.autoloaded_functions ();
}

// May be used to send an interrupt signal to the the interpreter from
// another thread (for example, the GUI).

void
interpreter::interrupt ()
{
  static int sigint = 0;
  static bool first = true;

  if (first)
    {
      octave_get_sig_number ("SIGINT", &sigint);
      first = false;
    }

  // Send SIGINT to Octave and (optionally) all other processes in its
  // process group.  The signal handler for SIGINT will set a global
  // variable indicating an interrupt has happened.  That variable is
  // checked in many places in the Octave interpreter and eventually
  // results in an interrupt_exception being thrown.  Finally, that
  // exception is caught and returns control to one of the
  // read-eval-print loops or to the server loop.  We use a signal
  // instead of just setting the global variables here so that we will
  // probably send interrupt signals to any subprocesses as well as
  // interrupt execution of the interpreter.

  pid_t pid
    = m_interrupt_all_in_process_group ? 0 : octave_getpid_wrapper ();

  octave_kill_wrapper (pid, sigint);
}

void
interpreter::pause ()
{
  // FIXME: To be reliable, these tree_evaluator functions must be
  // made thread safe.

  m_evaluator.break_on_next_statement (true);
  m_evaluator.reset_debug_state ();
}

void
interpreter::stop ()
{
  // FIXME: To be reliable, these tree_evaluator functions must be
  // made thread safe.

  if (m_evaluator.in_debug_repl ())
    m_evaluator.dbquit (true);
  else
    interrupt ();
}

void
interpreter::resume ()
{
  // FIXME: To be reliable, these tree_evaluator functions must be
  // made thread safe.

  // FIXME: Should there be any feeback about not doing anything if
  // not in debug mode?

  if (m_evaluator.in_debug_repl ())
    m_evaluator.dbcont ();
}

octave_value
interpreter::PS1 (const octave_value_list& args, int nargout)
{
  return m_input_system.PS1 (args, nargout);
}

std::string
interpreter::PS1 () const
{
  return m_input_system.PS1 ();
}

std::string
interpreter::PS1 (const std::string& s)
{
  return m_input_system.PS1 (s);
}

void
interpreter::set_PS1 (const std::string& s)
{
  m_input_system.set_PS1 (s);
}

octave_value
interpreter::PS2 (const octave_value_list& args, int nargout)
{
  return m_input_system.PS2 (args, nargout);
}

std::string
interpreter::PS2 () const
{
  return m_input_system.PS2 ();
}

std::string
interpreter::PS2 (const std::string& s)
{
  return m_input_system.PS2 (s);
}

void
interpreter::set_PS2 (const std::string& s)
{
  m_input_system.set_PS2 (s);
}

octave_value
interpreter::PS4 (const octave_value_list& args, int nargout)
{
  return m_evaluator.PS4 (args, nargout);
}

std::string
interpreter::PS4 () const
{
  return m_evaluator.PS4 ();
}

std::string
interpreter::PS4 (const std::string& s)
{
  return m_evaluator.PS4 (s);
}

void
interpreter::set_PS4 (const std::string& s)
{
  m_evaluator.set_PS4 (s);
}

// Provided for convenience.  Will be removed once we eliminate the
// old terminal widget.
bool
interpreter::experimental_terminal_widget () const
{
  if (! m_app_context)
    return false;

  // Embedded interpreters don't execute command line options.
  const cmdline_options& options = m_app_context->options ();

  return options.experimental_terminal_widget ();
}

void
interpreter::add_debug_watch_expression (const std::string& expr)
{
  m_evaluator.add_debug_watch_expression (expr);
}

void
interpreter::remove_debug_watch_expression (const std::string& expr)
{
  m_evaluator.remove_debug_watch_expression (expr);
}

void
interpreter::clear_debug_watch_expressions ()
{
  m_evaluator.clear_debug_watch_expressions ();
}

std::set<std::string>
interpreter::debug_watch_expressions () const
{
  return m_evaluator.debug_watch_expressions ();
}

void
interpreter::handle_exception (const execution_exception& ee)
{
  m_error_system.save_exception (ee);

  // FIXME: use a separate stream instead of std::cerr directly so that
  // error messages can be redirected more easily?  Pass the message
  // to an event manager function?
  m_error_system.display_exception (ee);

  recover_from_exception ();
}

void
interpreter::recover_from_exception ()
{
  if (octave_interrupt_state)
    m_event_manager.interpreter_interrupted ();

  can_interrupt = true;
  octave_interrupt_state = 0;
  octave_signal_caught = false;
  octave_restore_signal_mask ();
  catch_interrupts ();
}

void
interpreter::mark_for_deletion (const std::string& file)
{
  m_tmp_files.insert (file);
}

void
interpreter::cleanup_tmp_files ()
{
  m_tmp_files.cleanup ();
}

void
interpreter::quit (int exit_status, bool force, bool confirm)
{
  if (! force)
    {
      try
        {
          bool cancel = false;

          if (symbol_exist ("finish.m", "file"))
            {
              unwind_protect_var<bool> upv1 (m_executing_finish_script, true);
              unwind_protect_var<bool> upv2 (m_cancel_quit);

              evalin ("base", "finish", 0);

              cancel = m_cancel_quit;
            }

          if (cancel)
            return;

          // Check for confirmation.

          if (confirm && ! m_event_manager.confirm_shutdown ())
            return;
        }
      catch (const execution_exception&)
        {
          // Catch execution_exceptions so we don't throw an
          // exit_exception if there is an in finish.m.  But throw it
          // again so that will be handled as any other
          // execution_exception by the evaluator.  This way, errors
          // will be ignored properly and we won't exit if quit is
          // called recursively from finish.m.

          throw;
        }
    }

  throw exit_exception (exit_status);
}

void
interpreter::add_atexit_fcn (const std::string& fname)
{
  if (m_executing_atexit)
    return;

  m_atexit_fcns.push_front (fname);
}

bool
interpreter::remove_atexit_fcn (const std::string& fname)
{
  bool found = false;

  for (auto it = m_atexit_fcns.begin ();
       it != m_atexit_fcns.end (); it++)
    {
      if (*it == fname)
        {
          m_atexit_fcns.erase (it);
          found = true;
          break;
        }
    }

  return found;
}

// What internal options get configured by --traditional.

void
interpreter::maximum_braindamage ()
{
  PS1 (">> ");
  PS2 ("");
  PS4 ("");

  m_load_save_system.crash_dumps_octave_core (false);
  m_load_save_system.save_default_options ("-mat-binary");

  m_history_system.timestamp_format_string ("%%-- %D %I:%M %p --%%");

  m_error_system.beep_on_error (true);

  Fconfirm_recursive_rmdir (ovl (false));
  Foptimize_diagonal_matrix (ovl (false));
  Foptimize_permutation_matrix (ovl (false));
  Foptimize_range (ovl (false));
  Ffixed_point_format (ovl (true));
  Fprint_empty_dimensions (ovl (false));
  Fprint_struct_array_contents (ovl (true));
  Fstruct_levels_to_print (ovl (0));

  m_error_system.disable_warning ("Octave:abbreviated-property-match");
  m_error_system.disable_warning ("Octave:colon-nonscalar-argument");
  m_error_system.disable_warning ("Octave:data-file-in-path");
  m_error_system.disable_warning ("Octave:empty-index");
  m_error_system.disable_warning ("Octave:function-name-clash");
  m_error_system.disable_warning ("Octave:possible-matlab-short-circuit-operator");
}

void
interpreter::execute_pkg_add (const std::string& dir)
{
  try
    {
      m_load_path.execute_pkg_add (dir);
    }
  catch (const interrupt_exception&)
    {
      recover_from_exception ();
    }
  catch (const execution_exception& ee)
    {
      handle_exception (ee);
    }
}

// Execute commands from a file and catch potential exceptions in a consistent
// way.  This function should be called anywhere we might parse and execute
// commands from a file before we have entered the main loop in
// toplev.cc.

int
interpreter::safe_source_file (const std::string& file_name,
                               const std::string& context,
                               bool verbose, bool require_file)
{
  try
    {
      source_file (file_name, context, verbose, require_file);
    }
  catch (const interrupt_exception&)
    {
      recover_from_exception ();

      return 1;
    }
  catch (const execution_exception& ee)
    {
      handle_exception (ee);

      return 1;
    }

  return 0;
}

OCTAVE_END_NAMESPACE(octave)
